//plugin console mode
package main

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"log"
	"math/rand"
	"strings"
	"time"

	"github.com/gookit/color"
	runewidth "github.com/mattn/go-runewidth"
	"github.com/rocket049/gocui"
)

func init() {
	rand.Seed(time.Now().UnixNano())
	verbose = true
	step = 1
}

var (
	buf     string
	bot     Bot
	step    int
	verbose bool
)

func setIRCServer(g *gocui.Gui, v *gocui.View) {
	p := readEditor(v)
	if p == nil {
		bot.server = "chat.freenode.net:6667"
	} else {
		bot.server = string(p)
	}
	step++
	viewPrint(g, "Server", bot.server, false)
}

func setNick(g *gocui.Gui, v *gocui.View) {
	p := readEditor(v)
	if p == nil {
		bot.User = fmt.Sprintf("talker%d", rand.Uint32())
	} else {
		bot.User = string(p)
	}
	bot.Nick = bot.User
	step++
	viewPrint(g, "Nickname", bot.Nick, false)
}

func setRoom(g *gocui.Gui, v *gocui.View) {
	p := readEditor(v)
	if p == nil {
		bot.Channel = fmt.Sprintf("#xtalk%d", rand.Uint32())
	} else {
		bot.Channel = fmt.Sprintf("#xtalk%s", string(p))
	}
	step++
	viewPrint(g, "Room", strings.TrimPrefix(bot.Channel, "#xtalk"), false)
}

func setKey(g *gocui.Gui, v *gocui.View) {
	p := readEditor(v)
	if p == nil {
		return
	} else {
		bot.Crypto.SetKey(string(p))
		v.Editable = false
		step++
		setViewHead(g, false)
		//when receive join switch Editable to true
		//next goroutine doRecv
		go doRecv(g)
	}
}

func setViewHead(g *gocui.Gui, connected bool) {
	v, err := g.View("head")
	var stat string
	if connected {
		stat = color.FgGreen.Text("connected")
	} else {
		stat = color.FgRed.Text("connecting")
	}
	if err == nil {
		v.Clear()
		fmt.Fprintf(v, "%s / %s / %s    %s", bot.server,
			strings.TrimPrefix(bot.Channel, "#xtalk"), bot.Nick, stat)
	}
}
func setConnected(g *gocui.Gui) error {
	setViewHead(g, true)
	return nil
}
func setHeadText(g *gocui.Gui, msg string) {
	v, err := g.View("head")
	if err == nil {
		v.Clear()
		fmt.Fprint(v, color.FgGreen.Text(msg))
	}
}

type VOT struct {
	Name, Msg, Sep string
}

func (self VOT) Show(g *gocui.Gui) error {
	v, err := g.View("out")
	if err != nil {
		//log.Println("No output view")
		return nil
	}
	fmt.Fprintf(v, "%v:%v%v\n", color.FgGreen.Text(self.Name), self.Sep,
		color.FgYellow.Text(self.Msg))
	return nil
}
func viewPrint(g *gocui.Gui, name, msg string, newline bool) {
	var out VOT
	out.Name = name
	out.Msg = msg
	if newline {
		out.Sep = "\n"
	} else {
		out.Sep = " "
	}
	g.Update(out.Show)
}

//doRecv work in goroutine
func doRecv(g *gocui.Gui) {
	bot.Connect()
	bot.Command("MODE", bot.Channel, "+s")
	for {
		message, err := bot.Recv()
		if err != nil {
			//log.Println(err)
			//log.Println("Exit Now!")
			viewPrint(g, "sys", "Exit Now!", false)
			break
		}

		if message.Command == "JOIN" {
			//connected
			//log.Printf("%v %v\n", message.Prefix.Name, message.Command)
			viewPrint(g, message.Prefix.Name, message.Command, false)
			if step < 6 && message.Prefix.Name == bot.Nick {
				step++
				g.CurrentView().Editable = true
				g.Update(setConnected)
			}
		} else if message.Command == "QUIT" {
			viewPrint(g, message.Prefix.Name, message.Command, false)
		} else if message.Command == "PING" {
			bot.Send(fmt.Sprintf("PONG %d", time.Now().UnixNano()))
			//log.Println("SEND: PONG")
		} else if message.Command == "PRIVMSG" {
			// Do Something with this msg
			rmsg := message.Params[1]
			dmsg := bot.Crypto.Decode(rmsg)
			var msg string
			if dmsg == nil {
				msg = fmt.Sprintf("%s %s", color.FgRed.Text("[not secret]"), rmsg)
			} else {
				msg = string(dmsg)
			}
			//var name1, err = hex.DecodeString(strings.TrimPrefix(message.Prefix.Name, "user"))
			name1 := message.Prefix.Name
			if err == nil {
				//log.Printf("%v:%v\n", string(name1), msg)
				viewPrint(g, string(name1), msg, true)
			}
		} else if verbose {
			//log.Printf("%v\n", message)
			msg := fmt.Sprintf("%s:%s", message.Command, strings.Join(message.Params, ","))
			viewPrint(g, message.Prefix.Name, msg, false)
		}
	}
	g.Close()
}

func quit(g *gocui.Gui, v *gocui.View) error {
	bot.Close()
	ov, _ := g.View("out")
	buf = ov.Buffer()
	g.Close()
	return gocui.ErrQuit
}

func modifyCJK(p []byte) []byte {
	buf := bytes.NewBuffer(bytes.Trim(p, " \n\t"))
	sz := len(buf.String())
	buf1 := bytes.NewBufferString("")
	var r rune
	var wr bool
	for i := 0; i < sz; i++ {
		r, _, _ = buf.ReadRune()
		if r != rune(0) && wr == false {
			buf1.WriteRune(r)
		} else if wr == true {
			if r != rune(' ') {
				buf1.WriteRune(r)
			}
		}
		wr = runewidth.RuneWidth(r) > 1
	}
	return buf1.Bytes()
}
func readEditor(v *gocui.View) []byte {
	var b = make([]byte, 300)
	n, _ := v.Read(b)
	if n > 0 {
		return modifyCJK(b[:n])
	} else {
		return nil
	}
}

func doSay(g *gocui.Gui, cv *gocui.View) {
	v, err := g.View("out")
	if cv != nil && err == nil {
		p := cv.ReadEditor()
		//var p = readEditor(cv)
		if p != nil {
			if strings.HasPrefix(string(p), "/") {
				bot.Send(string(p[1:]))
				if string(p[1:]) == "quit" {
					bot.Close()
				}
				return
			}
			v.Write([]byte("你:"))
			v.Write(append(p, '\n'))
			bot.PrivMsgTo(bot.Channel, string(p))
		}
		v.Autoscroll = true
	}
}

func viewUpdate(g *gocui.Gui, cv *gocui.View) error {
	switch step {
	case 1:
		setIRCServer(g, cv)
		setHeadText(g, "输入用户名（默认值：随机名称）")
	case 2:
		setNick(g, cv)
		setHeadText(g, "输入房间名（默认值：随机名称）")
	case 3:
		setRoom(g, cv)
		setHeadText(g, "输入密钥（长度必须大于0）")
	case 4:
		setKey(g, cv)
	case 6:
		doSay(g, cv)
	}
	l := len(cv.Buffer())
	cv.MoveCursor(0-l, 0, true)
	cv.Clear()
	return nil
}

func viewUpScroll(g *gocui.Gui, cv *gocui.View) error {
	v, err := g.View("out")
	v.Autoscroll = false
	ox, oy := v.Origin()
	if err == nil {
		v.SetOrigin(ox, oy-1)
	}
	return nil
}

func viewDownScroll(g *gocui.Gui, cv *gocui.View) error {
	v, err := g.View("out")
	_, y := v.Size()
	ox, oy := v.Origin()
	lnum := len(v.BufferLines())
	if err == nil {
		if oy > lnum-y-1 {
			v.Autoscroll = true
		} else {
			v.SetOrigin(ox, oy+1)
		}
	}
	return nil
}

func viewOutput(g *gocui.Gui, x0, y0, x1, y1 int) error {
	v, err := g.SetView("out", x0, y0, x1, y1)
	if err != nil {
		if err != gocui.ErrUnknownView {
			return err
		}
		v.Wrap = true
		v.Overwrite = false
		v.Autoscroll = true
		v.SelBgColor = gocui.ColorRed
		v.Title = "Messages"
	}
	return nil
}
func viewInput(g *gocui.Gui, x0, y0, x1, y1 int) error {
	if v, err := g.SetView("main", x0, y0, x1, y1); err != nil {
		if err != gocui.ErrUnknownView {
			return err
		}
		//当 err == gocui.ErrUnknownView 时运行
		v.Editable = true
		v.Wrap = true
		v.Overwrite = false
		if _, err := g.SetCurrentView("main"); err != nil {
			return err
		}
	}
	return nil
}

func viewHead(g *gocui.Gui, x0, y0, x1, y1 int) error {
	if v, err := g.SetView("head", x0, y0, x1, y1); err != nil {
		if err != gocui.ErrUnknownView {
			return err
		}
		v.Wrap = false
		v.Overwrite = true
		//fmt.Fprint(v, "header")
		msg := "输入IRC服务器（默认：'chat.freenode.net:6667'）"
		setHeadText(g, msg)
	}
	return nil
}
func layout(g *gocui.Gui) error {
	maxX, maxY := g.Size()
	if err := viewHead(g, 1, 1, maxX-1, 3); err != nil {
		return err
	}
	if err := viewOutput(g, 1, 4, maxX-1, maxY-4); err != nil {
		return err
	}
	if err := viewInput(g, 1, maxY-3, maxX-1, maxY-1); err != nil {
		return err
	}
	return nil
}

var pos int

func pasteUP(g *gocui.Gui, cv *gocui.View) error {
	v, err := g.View("out")
	if err != nil {
		fmt.Fprintf(cv, "error:%s", err)
		return nil
	}
	bls := v.BufferLines()
	lnum := len(bls)
	if pos < lnum-1 {
		pos++
	}
	cv.Clear()
	fmt.Fprintf(cv, "%s", bls[lnum-pos-1])
	return nil
}

func pasteDown(g *gocui.Gui, cv *gocui.View) error {
	v, err := g.View("out")
	if err != nil {
		fmt.Fprintf(cv, "error:%s", err)
		return nil
	}
	if pos > 0 {
		pos--
	}
	bls := v.BufferLines()
	lnum := len(bls)
	cv.Clear()
	fmt.Fprintf(cv, "%s", bls[lnum-pos-1])
	return nil
}

func RunMain() {
	g, err := gocui.NewGui(gocui.OutputNormal)
	if err != nil {
		log.Panicln(err)
	}
	g.Cursor = true
	g.Mouse = false
	g.ASCII = false

	g.SetManagerFunc(layout)
	//layout(g)

	if err := g.SetKeybinding("main", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
		log.Panicln(err)
	}

	if err := g.SetKeybinding("main", gocui.KeyEnter, gocui.ModNone, viewUpdate); err != nil {
		log.Panicln(err)
	}
	if err := g.SetKeybinding("main", gocui.KeyPgup, gocui.ModNone, viewUpScroll); err != nil {
		log.Panicln(err)
	}
	if err := g.SetKeybinding("main", gocui.KeyPgdn, gocui.ModNone, viewDownScroll); err != nil {
		log.Panicln(err)
	}
	if err := g.SetKeybinding("main", gocui.KeyArrowDown, gocui.ModNone, pasteDown); err != nil {
		log.Panicln(err)
	}
	if err := g.SetKeybinding("main", gocui.KeyArrowUp, gocui.ModNone, pasteUP); err != nil {
		log.Panicln(err)
	}

	if err := g.MainLoop(); err != nil {
		log.Println(err)
	}
	ioutil.WriteFile("chat.log", []byte(buf), 0644)
}

func main() {}
